'''
Created on Apr 23, 2012

@author: root
'''
from Base import Base
from FileOps import FileOps
from Module import Module
#from Project import Project
#from Role import Role
from distutils import version
import Queue
import operator
#import os
#import rpm
import tarfile
import threading
import time
#import yum


class Software2(Base):

    remoteProjectBaseDir = "/tmp/pachyderm"
    remoteProjectDir = ""
    binDir = ""
    binName = ""
    nameNode = object
    secondary_nameNode = object
    backup_nameNode = object
    jobTracker = object
    taskTracker = object
    dataNode = object
    Nodes = []
    Roles = []
    hadoopPath = ""
    hadoopFiles = []
   
    fileops = None

    projectFiles = []
    projectPath = ""
    
    swFilenames = []
    swPaths = []
    
    binPath = ""
    modInfoQueue = Queue.Queue()
    installQueue = Queue.Queue()
    verifyQueue = Queue.Queue()
    projectName = ""
    
    localDir = ""
    localPath = ""
    
    def PrepareInstallFile(self, binPath):
        
        ''' Make this copy the file to a known directory and then spin it out '''
        ''' Then check in that directory whent starting '''
        
        '''  Uncompress then match to hadoop.info and build paths'''
        
        ### ADD SUDO!!
        binComponents = binPath[1:].split('/')
        self.binName = binComponents[len(binComponents) - 1]
        binComponents.remove(self.binName)
        for comp in (binComponents):
            self.binDir = self.binDir + "/" + comp
        print "    * Starting " + str(self.projectName) + " Software Install"
        
      
        try:
            tarfile.is_tarfile(binPath)
            self.LogInfo("Verifying Install File")
            print "    * Install File Verified as Valid Tar File"
        except:
            self.LogError(binPath + " Not a Valid Tar File")
            print "    * ERROR: Specified Install File id Not a Valid Tar File"
            return -1
        print "    * Extracting Tar File for Installation"
        
        self.localDir = "./software/" + self.projectName
        self.localPath = self.localDir + "/" + self.binName

        self.fileops.MakeDir(self.localDir)
        self.fileops.LocalPush(binPath, self.localDir + "/" + self.binName)
        
        
        swInstallerTAR = tarfile.open(self.localPath, "r:gz")
        self.LogInfo("Extracting Install File...")
        self.LogInfo("Determining Files to extract.")

        for member_info in swInstallerTAR.getmembers():
            if (str(member_info.type) == "5"):
                self.swPaths.append(str(member_info.name))
                
            elif (str(member_info.type) == "0"):
                self.swFilenames.append(str(member_info.name))   
        # Fix for no directories in gzip
        if (len(self.swPaths) == 0):
            self.swPaths.append(".")

        try:  
            
            self.LogInfo("File Extraction Started")
            swInstallerTAR.extractall(self.localDir)
            self.LogInfo("File Extraction Complete")
            print "        * Tar File Extraction Complete"

        except:
            self.LogError("Could Not Extract GPHD Tar " + str(binPath))
            print "        * ERROR: Could not extract Tar File"
            return -1
         

    
    def PushProjectThead(self, node, Project):
        threadCount = 0
        Threads = []
        tryCnt = 0
        for personality in node.personalities:
            for role in Project.Roles:
                if (str(personality) == str(role.personality)):
                    for module in role.Modules:
                        retry = True
                        while (retry) and (tryCnt < 3):
                            localPath = str(self.localDir) + "/" + str(module.filePath) 
                            
#                            fileName = (str(module.filePath).split('/'))[len(str(module.filePath).split('/'))-1]
                            remotePath = str(self.remoteProjectDir) + "/" + str(module.filePath)
                            self.LogInfo("Transferring " + str(localPath) + " to " + str(node.hostName) + ":" + str(remotePath))
                            (status, results) = self.SSHPut(node.hostName, self.installUser, self.installPW, localPath, remotePath)
                            if (status == 0):
                                self.LogInfo(str(node.hostName) + " : " + str(results))
                                print "        * Copied " + str(module.rpmname) + " to " + str(node.hostName)
                                retry = False
                                tryCnt = 0
                            else:
                                self.LogError("Could not Copy Files to " + str(node.hostName) + " : " + str(results) + " ; Exiting")
                                print "        * ERROR:  Could Not Copy " + str(module.rpmname) + " to " + str(node.hostName + ". Will Retry")
                                retry = True
                                tryCnt += 1
                 
                       
    def PushProject(self, Nodes, Project):
        pushStatus = 0
        self.LogInfo("Pushing Project Software to Cluster Nodes")
        print "    * Pushing Project Software to Cluster Nodes"
        threadCount = 0
        Threads = []
        print "        * Creating Temporary Directories on Cluster Nodes for Push Target"
        for node in Nodes:
            for path in Software2.swPaths:
                self.LogInfo("Creating Temporary Directory on Node: " + str(node.hostName) + ":" + str(self.remoteProjectDir) + "/" + str(path))
                #mkdirCMD = str(self.installSudo) + "/bin/mkdir -p "+ self.remoteProjectDir+"/"+str(path)
                mkdirCMD = "/bin/mkdir -p " + self.remoteProjectDir + "/" + str(path)

                self.LogInfo(mkdirCMD)
                Threads.append(threading.Thread(target=self.SSHExec, args=(mkdirCMD, node.hostName, self.installUser, self.installPW)))
                Threads[threadCount].daemon = True
                Threads[threadCount].start()
                threadCount = threadCount + 1
        for thread in Threads:
            thread.join()
            (hostName, username, status, results) = self.returnQueue.get()
            if (status == 0):
                self.LogInfo("Temporary Directories created on " + str(hostName))
            else:
                self.LogError(str(hostName) + " : " + str(results) + " ; Exiting")
                pushStatus = -1
                
        if (pushStatus == 0):
            self.LogInfo("Temporary Directories created on all Cluster Hosts")
        else:
            return pushStatus
           
             # Cycle through Nodes and Personalities and apply correct ones 

        self.ClearReturnQueue()
        threadCount = 0
        Threads = []
        for node in Nodes:
            Threads.append(threading.Thread(target=self.PushProjectThead, args=(node, Project))) 
            Threads[threadCount].daemon = True
            Threads[threadCount].start()
            threadCount = threadCount + 1    
        for thread in Threads:
            thread.join()
            
            (hostName, username, status, results) = self.returnQueue.get()
            pushStatus = 0

           
        if (pushStatus == 0):
            self.LogInfo(" Project Push Completed")
            print "    *  Project Push Completed"
        return pushStatus    
  
  
  
    def InstallModules(self, node, Project,retry):
        
        
        ''' Roles and Modules now have an install order associated with them'''
        ''' REWWRITE THIS MODLE..   TRY TO KEEP AN ARRAY OF ONES THAT DIDNT WORK'''
       
    
        '''           status ->    0 Not Installed
                                 1 Installed Version is older than RPM
                                2 Installed Version the Same or Newer'''
                  
                                 
        (Project.Roles).sort(key=operator.attrgetter("installOrder"))                            
        
            
        for personality in node.personalities:
            for role in Project.Roles:
                (role.Modules).sort(key=operator.attrgetter("installOrder"))
                if (str(personality) == str(role.personality)):
                    
                    for module in role.Modules:
                        if (retry):
                            module.dependencyCheck = False
                        
                        remotePath = str(self.remoteProjectDir) + "/" + str(module.filePath)
                        if (module.status < 2):
                            self.ModuleStatus(module, node.hostName)
                        
                        if (module.status >= 0) and (module.status < 2):
                            if (module.dependencyCheck):
                                installCMD = str(self.installSudo) + "rpm -U " + remotePath
                            else:
                                installCMD = str(self.installSudo) + "rpm -U --nodeps " + remotePath
                            self.LogInfo(installCMD)
                            (status, results) = self.SSHExec(installCMD, node.hostName, self.installUser, self.installPW)

                            if (status == 0):
                                print "        * Installed " + module.rpmname + " on " + node.hostName
                                self.LogInfo("Status:" + str(status) + "::Module.Status:" + str(results))
                                module.status = 2
                            else:
                                print "        * Could Not Install " + module.rpmname + " on " + node.hostName + ". Will Retry"
                                self.LogInfo("Status:" + str(status) + "::Module.Status:" + str(results))
                                module.status = -1
                        elif (module.status >=2):
                            if (not retry):
                                print "        * " + module.rpmname + " is already installed on " + node.hostName
                                self.LogInfo(module.rpmname + " is already installed on " + node.hostName)
                        else:
                            print "        * Error Trying to install "+ module.rpmname
                                                               
        
    def InstallProject(self, Nodes, Project):
        installStatus = 0
        threadCount = 0
        Threads = []
        self.ClearReturnQueue()
        self.LogInfo("Starting  " + Project.name + " Project Installation")
        print "    * Starting " + (Project.name).upper() + "  Project Installation"
      
        for node in Nodes:
            Threads.append(threading.Thread(target=self.InstallModules, args=(node, Project,False)))
            Threads[threadCount].daemon = True
            Threads[threadCount].start()     
            threadCount = threadCount + 1
        for thread in Threads:
            thread.join()
        
        self.ClearReturnQueue()



        ### This needs to be UN THREADED
        
        print "    * Checking for Modules that Did not Install Properly"
        for node in Nodes:
            Threads.append(threading.Thread(target=self.InstallModules, args=(node, Project,True)))
            Threads[threadCount].daemon = True
            Threads[threadCount].start()     
            threadCount = threadCount + 1
        for thread in Threads:
            thread.join()
        
        
        #Check all modules for Valid installation Status
        errorCnt = 0
        for node in Nodes:
            for role in node.Roles:
                for mod in role.Modules:
                    if (mod.status <= 0):
                        if (errorCnt == 0):
                            print "    * List of Modules that would not Install Properly"

                        print "        * ERROR: " + mod.rpmname + " Failed to Install on " + node.hostName
                        self.LogError(mod.rpmname + " Failed to Install on " + node.hostName)
                        installStatus = -1
                        errorCnt += 1
        
        if (installStatus == 0):
            self.LogInfo("All Modules Correctly installed")
        else:
            print "\n\n--------------------------------------------------------------------------------------------------"
            print "        "
            print "    PLEASE RESOLVE ANY DEPENDENDENCY ISSUES, INSTALL THESE RPMS MANUALLY, AND RESTART INSTALLATION"
            print " "
            print " ---------------------------------------------------------------------------------------------------"
            self.LogError("Some Modules Failed to Install Correclty")
        return installStatus                
        
                    
       

    def ModuleStatus(self, module, hostname):
        
        '''
        This script takes in a package file name on the command line, 
        loads in the header for that package, and looks up all packages
        of the same name installed in the RPM database. For each match,
        this script compares the packages to see which is newer.
        Return True if Installed Package is Same or Newer
        
        status ->   
                                -1 Unknown
                                 0 Not Installed
                                 
                                 1 Installed Version is older than RPM
                                 2 Installed Version the Same or Newer'''
        
        '''Check to See if RPM is already Installed
             if it is, return that info
             if not
                 query further to get more detailed info.
                 '''
        
        
        # CHECK IF RPM IS INSTALLED
        # module.rpmname is name minus extension
    
        queryCMD = str(self.installSudo) + "rpm -q " + str(module.rpmname)
        (status, results) = self.SSHExec(queryCMD, hostname, self.installUser, self.installPW, True)
        self.LogInfo(queryCMD+" : "+str(status)+" : " + str(type(results)) + " : "+str(results))
        if (status == 0):
            module.status = 2
        elif (status == -1):
            module.status = -1
            self.LogError("Trouble with SSH Connectivity")
        else:
        # Query to get Detailed RPM Info
            queryCMD = str(self.installSudo) + "rpm -qp --qf %{NAME}:%{EPOCH}:%{VERSION}:%{RELEASE} " + str(self.remoteProjectDir + "/" + str(module.filePath))
            (status, results) = self.SSHExec(queryCMD, hostname, self.installUser, self.installPW)
            self.LogInfo(queryCMD+" : "+str(status)+" : " + str(type(results)) + " : "+str(results))
            # Found RPM for query.
            # RESULTS is LIST
            if (status == 0 ):
                results = results[0]

                rpmInfo = str(results).split(":")
                name = rpmInfo[0]
                epoch = rpmInfo[1]
                versionStr = rpmInfo[2]
                release = rpmInfo[3]
                rpmVersion = str(epoch) + ":" + str(versionStr) + "-" + str(release)
                
                
                # Query Package Short Name to see if A different Version is Installed
                queryCMD = str(self.installSudo) + "rpm -q --qf %{NAME}:%{EPOCH}:%{VERSION}:%{RELEASE} " + str(name)
                (status, results) = self.SSHExec(queryCMD, hostname, self.installUser, self.installPW, True)
                self.LogInfo(queryCMD+" : "+str(status)+" : " + str(type(results)) + " : "+str(results))
                if (status==0):
                    ## CHECK VERSIONS
                    
                    # Change List to String
                    results = results[0]
                    installedInfo = str(results).split(":")
                    installedName = installedInfo[0]
                    installedEpoch = installedInfo[1]
                    installedVersionStr = installedInfo[2]
                    installedRelease = installedInfo[3]
                    installedrpmVersion = str(installedEpoch) + ":" + str(installedVersionStr) + "-" + str(installedRelease)

                    self.LogInfo("Compare Versions : "+rpmVersion+" : "+installedrpmVersion)

                    # Check Installed version versus teh RPM Version 
                    
                    if version.LooseVersion(rpmVersion) < version.LooseVersion(installedrpmVersion):
                        self.LogInfo("Installed Version is Same or newer than RPM.  No Install Necessary")
                        module.status = 2
                        module.name = installedName
                    elif (version.LooseVersion(versionStr) == version.LooseVersion(installedVersionStr)):
                        self.LogInfo("Installed Version is Close to the RPM version: Same Version Different Release)::"+str(versionStr)+":"+str(installedVersionStr))
                        print                      "--------------------------------------------------------"
                        choice = self.query_yes_no("Installed Version of "+ module.rpmname +" is the same as the RPM version (Dot release difference): Skip Install?","no")
                        print                      "--------------------------------------------------------"

                        if (choice):
                            self.LogInfo("Skipping Install of "+module.rpmname)
                            module.status = 2    
                            module.name  = name
                        else:
                            module.status = 1    
                            module.name  = name       
                    else:   
                        self.LogInfo("Installed Version is Older than RPM. Upgrade Required")
                        module.status = 1    
                        module.name  = name    
               
                else:
                    module.name = name
                    module.status=0
     
            else:
                self.LogError("Trouble Querying Information about "+ str(module.filePath))
                module.status = -1
 





    def QueryThreader(self, node, Project):
        installStatus = 0
        for personality in node.personalities:
                for role in Project.Roles:
                    if (str(personality) == str(role.personality)):
                        for module in role.Modules:
                            remotePath = str(module.filePath).split('/')
                            remotePkg = remotePath[len(remotePath) - 1][:-4]
                            if not (self.RPMQuery(str(remotePkg), str(node.hostName))):
                                installStatus = -1
        self.verifyQueue.put((node.hostName, installStatus))
                     

    def VerifyProject(self, Nodes, Project):
        verifyStatus = 0
       
        threadCount = 0
        Threads = []
        self.ClearReturnQueue()

        print "    * Verifying " + Project.name + " Project Installation"
        for node in self.Nodes:
            Threads.append(threading.Thread(target=self.QueryThreader, args=(node, Project)))
            Threads[threadCount].daemon = True
            Threads[threadCount].start()     
            threadCount = threadCount + 1
        for thread in Threads:
            thread.join()
            
        while (not self.verifyQueue.empty()):
            (hostName, status) = self.verifyQueue.get()
            if (status == 0):
                self.LogInfo("Project Installation Verified on " + str(hostName))
                print "        * Project Installation Verified on " + str(hostName)
            else:
                self.LogError("Project Not Installed Correctly on " + str(hostName))
                print "        * Project Not Installed Correctlyon  " + str(hostName)

                verifyStatus = -1
                
                
        if (verifyStatus == 0):
            self.LogInfo("Install Complete")
        return verifyStatus


     
    def __init__(self, Users, projectName):
   
        Base.__init__(self)
        ''' Take the entry from the -g entry and parse the components '''
        self.SetUsers(Users)
        self.fileops = FileOps()
        self.projectName = projectName
        self.remoteProjectDir = str(self.remoteProjectBaseDir) + "/" + str(self.projectName)

      

        
#        self.binDir = binDir
#        self.binName = binName

        
